//: [Previous](@previous)
/*
 常见的错误类型
 1. 语法错误(编译报错)
 2. 逻辑错误
 3. 运行时错误(可能会导致闪退,一般也叫异常)
 */
//: ## 自定义错误
// Swift中可以通过Error协议自定义运行时的错误信息
//举例 mj老师自己写的

enum SomeError : Error {
    case illegalArg(String) //非法参数,string写上哪个参数异常
    case outOfBounds(Int, Int) //越界,第一个int:数组的个数大小 第二个int:越级了的下标
    case outOfMemory //内存溢出
}

// 函数内部通过 throw 抛出自定义Error, 可能会抛出Error的函数的声明必须加上 throws
func divide(_ num1: Int, _ num2: Int) throws -> Int {//因为这个函数有可能抛出Error,所以要在声明末加上throws
    if num2 == 0 {
        throw SomeError.illegalArg("0不能作为除数")
    }
    return num1 / num2
}
// 需要使用try调用可能会抛出Error的函数
//var result = try divide(20, 0)

//: ## do catch
// 可以使用do-catch 捕抓 Error
// 抛出Error后, try下一句直到作用域结束的代码都会停止运行
/*
 现实中用途:
 一般用于发现了错误还想补救不是直接要让程序奔溃.这种捕捉到错误,可以抛出以使程序还能继续运行,可以通过弹窗的形式让客户知道错误原因啊(比如不能输入负号或者小数啥的,再重新操作一次.或者关掉程序重新进来操作一次,或者重新刷新一下请求)
 */
func test() {
    print(1)
    do {
        print(2)
        //需要执行有可能抛出Error的代码, 前面加上try
        try divide(200, 0)
        //当try的异常发生后,这个作用域之后的代码就不会再运行,所以print(3)就不会运行了
        print(3)
    } catch SomeError.illegalArg(let msg) {
        print("参数异常:", msg)
    } catch SomeError.outOfBounds(let size, let index) {
        print("下标越界:", "size = \(size)", "index = \(index)")
    } catch SomeError.outOfMemory {
        print("内存溢出")
    } catch {
        print("其他错误")
    }
    print(4)
}
test()
//1
//2
//参数异常: 0不能作为除数
//4
//:## 处理Error
/*
 发生了Error异常,一定需要处理,不然会报错,当前层的方法处理不了,就需要往上一层抛出让上一层函数去处理,直到main函数
 处理Error的2种方式
 1. 通过do-catch捕捉Error
 2. 不捕捉Error, 在当前函数的声明处增加throws, Error将自动抛给上层函数(就是谁调用你的给谁), 这种情况下最外层函数要写上 try 去调用
   - 如果最顶层函数(main函数)依然没有捕捉Error, 那么程序将终止(奔溃)
 */

print("------")
func test2() throws {
    print(1)
    try divide(20, 0)
    print(2)
}
//try test2()
//1
//报错Playground execution terminated: An error was thrown and was not caught:
print("------")
// 高级写法,直接把枚举写到catch里面自动匹配
func test3() throws {
    print(1)
    do {
        print(2)
        try divide(20, 0)
        print(3)
    } catch let error as SomeError {
        print(error)
    }
    print(4)
}
try test3()
//1
//2
//illegalArg("0不能作为除数")
//4
//直接判断是不是这个类型的错误
/*
 do {
    try divide(20, 0)
 } catch is SomeError {
    print("是SomeError这个类型的错误")
 }
 */
print("------")
func test4() throws{
    print(1)
    try test5() //已经抛出错误,作用域内的后面代码就不会再执行
    print(2)
}
func test5() throws{
    print(3)
    try test6() //因为test6有throws的声明,所以是有可能要抛出异常的,调用这种都要加上try
    print(4)
}
func test6() throws{//遇到对应的Error就处理,不是就向上一层抛出
    print(5)
    do {
        print(6)
        try divide(20, 0)
    } catch is SomeError {
        print("is SomeError ")
        print(7)
    }
    print(8)
}
try test4()
//1
//3
//5
//6
//is SomeError
//7
//8
//4
//2

//还有一种处理方式
//do {
//    try divide(200, 0)
//} catch let error {
//    switch error{
//    case let SomeError.illegalArg(msg):
//        print("参数异常:", msg)
//    default:
//        print("其他错误")
//    }
//}
//:## try? / try!
// 可以使用try? / try! 调用可能会抛出Error的函数, 这样就不用去处理Error
func test7() {
    print(1)
    var result1 = try? divide(20, 10) //int? 因为结果有可能是数字int也可能是没有就是nil,所以是int的可选项,最后返回是Optional(2)
    var result2 = try? divide(20, 0) //nil, 没有结果,可选项直接返回nil
    var result3 = try! divide(20, 10) //2, int, !隐式解包
//    var result4 = try! divide(20, 0) //返回的是nil,使用隐式解包是报错
    print(2)
}
test7()
// 👇🏻2种写法是等价的 a==b
var a = try? divide(20, 0)

var b: Int?
do {
    b = try divide(20, 0) //b有可能是int也有可能是nil,但是由于b是int?可选项,赋值后还是可选项,所以和第一种方法一个原理
} catch {
    b = nil
}
//: ## rethrows
//rethrows标明: 函数本身不会抛出错误, 但调用闭包参数抛出错误, 那么它会将错误向上抛(其实就是表示因为调用了参数导致的异常)
func exec(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows {
    print(try fn(num1, num2))
}
//try exec(divide, 20, 0)
//Playground execution terminated: An error was thrown and was not caught:▿ SomeError- illegalArg : "0不能作为除数
//:## defer
/*
 defer语句: 用来定义以任何方式(抛错误, return等)离开代码块前必须要执行的代码(其实就是整个代码块倒数第一句执行的代码,一般是一些保证要执行到的收尾的代码)
 defer语句将延迟至当前作用域结束之前执行
 */
//举例需求:打开一个文件夹,返回这个文件夹的编号(没有具体实现,只是用来说明defer语句的作用)
func open(_ filename: String) -> Int {
    print("open")
    return 0
}
//根据编号,关闭这个文件夹
func close(_ file: Int) {
    print("close")
}
// 处理文件的过程
func processFile(_ fileName: String) throws {
    let file = open(fileName) //假设这里是打开文件了
    
    //这里提前写好离开这个方法的最后一步,确保离开这个方法的最后一步是close了文件
    defer {
        close(file)
    }
    
    //假设使用文件的过程...
    try divide(20, 0) //这里假设出现了问题导致方法直接被抛出异常
    
    //正常情况下,close将会在这里调用
    
}
try processFile("text.txt") //先open,再close,最后面才是报错,defer确保了报错前一定执行到了代码
//open
//close
//Playground execution terminated: An error was thrown and was not caught:
//▿ SomeError
//  - illegalArg : "0不能作为除数"

// defer语句的 执行顺序 与 定义顺序 相反(要知道)
func fn1() {print("fn1")}
func fn2() {print("fn2")}
func test10() {
    defer { fn1() }
    defer { fn2() }
}
test10()
//fn2
//fn1 先写的defer后执行
//: ## assert(断言)
/*
 断言: 就是不符合指定条件,直接程序报错闪退,和do-catch的区别,断言是直接闪退无法抛出无法捕抓,一旦触发直接程序整个奔溃停止运行,无法像do-catch一样捕抓抛出处理还能继续运行
 很多编程语言都有断言机制:不符合指定条件就抛出运行时错误, 常用于调试(Debug)阶段的条件判断
 默认情况下,Swift的断言只会在Debug模式下生效, Release模式下会自动忽略
 
 当有特别的需求,可以增加指令令断言在Release模式下也能实现(一般是判断后台的值,不符合的话就会直接影响程序了,只能报错保证安全)👇🏻有截图资源在resources
 Build Settings -> Swift Flags
 -assert-config Release : 强制关闭断言
 -assert-config Debug : 强制开启断言
 */
func divideAssert(_ v1: Int, _ v2: Int) -> Int {
    assert(v2 != 0, "断言生效:除数不能为0") //v2!=0,就程序正常.但是当不满足这个条件是,程序就不应该正常,所以断言不满足就奔溃
    return v1 / v2
}
divideAssert(20, 2)
//divideAssert(20, 0) //触发断言
//: ## fatalError
/*
 如果遇到严重问题, 希望结束程序运行时, 可以直接使用fatalError函数抛出错误(这是无法通过do-catch捕捉的错误)
 和assert断言的区别就是,fatalError是不分Debug或者Release模式的,一律肯定执行,就是直接杀死程序
 
使用了fatalError函数, 就不需要写本来还应该写的return, 因为整个程序都有杀死了 ,本来的那些规则就无所谓了
 */
func fata(_ num: Int) -> Int {
    if num >= 0 {
        return 1
    }
    fatalError("num不能少于0")//直接程序就会被杀死了,所以没必要写else的那个return
}
/*
 在某些不得不实现, 但又不希望别人调用的方法, 可以考虑内部使用fatalError函数
 */
class faPer { required init() {} } //创建一个类,指定了初始化器, 那么它的子类就必须实现这个初始化器
class faStu : faPer {
    required init() {
        fatalError("don't call faStu.init") //不允许别人直接调用这个类的init()方法
    }
    init(score: Int) {}
}
var faS1 = faStu(score: 10) //成功
var faS2 = faStu()//触发fatalError,奔溃

func test11() -> Int {
    #warning("暂时没想到方法要写什么,但是这个方法必须先写,而且还是个有返回值的方法")
    
    fatalError() //⚠️小技巧:用于占位,这个时候就可以使用fatalError使代码运行到了的时候就报错,这样子自己又能知道有这个方法的存在,也能不用在此时写上不知道要写什么的返回值
}
//: ## do (单独的do:局部作用域)
class Dog {
    var age = 10
    func run() {}
}

do {
    let dog1 = Dog()
    dog1.age = 10
}

do {
    let dog2 = Dog()
    dog2.age = 20
}
// 这样子就可以被类似的代码区分开, 现实中开发很容易随复便赋值类似的代码,然后后面修改的时候漏了其中一处地方的dog1,出现没想到的bug





//: [Next](@next)
